home *** CD-ROM | disk | FTP | other *** search
/ Revista do CD-ROM 97 / CD-ROM 97 / CD-ROM 97.iso / internet / ghostzilla / ghsetup.exe / chrome / comm.jar / content / communicator / sidebar / sidebarOverlay.js < prev    next >
Encoding:
JavaScript  |  2002-04-23  |  48.7 KB  |  1,541 lines

  1. /* -*- Mode: Java; tab-width: 4; insert-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Mozilla Public License
  4.  * Version 1.1 (the "License"); you may not use this file except in
  5.  * compliance with the License. You may obtain a copy of the License at
  6.  * http://www.mozilla.org/MPL/
  7.  *
  8.  * Software distributed under the License is distributed on an "AS IS"
  9.  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  10.  * the License for the specific language governing rights and
  11.  * limitations under the License.
  12.  *
  13.  * The Original Code is Mozilla Communicator client code, released
  14.  * March 31, 1998.
  15.  *
  16.  * The Initial Developer of the Original Code is Netscape
  17.  * Communications Corporation. Portions created by Netscape are
  18.  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  19.  * Rights Reserved.
  20.  */
  21.  
  22. // xul/id summary:
  23. //
  24. // <box> sidebar-box
  25. //    <splitter> sidebar-panels-splitter
  26. //       <box> sidebar-panels-splitter-box*
  27. //    <sidebarheader> sidebar-title-box
  28. //       <menubutton> sidebar-panel-picker*
  29. //          <menupopup> sidebar-panel-picker-popup
  30. //    <box> sidebar-panels
  31. //       <template> sidebar-template*
  32. //          <box> sidebar-iframe-no-panels
  33. // <splitter> sidebar-splitter
  34. // <menupopup> menu_View_Popup*
  35. //    <menuitem> sidebar-menu
  36.  
  37. //////////////////////////////////////////////////////////////
  38. // Global variables
  39. //////////////////////////////////////////////////////////////
  40.  
  41.  
  42. var gCurFrame;
  43. var gTimeoutID = null;
  44. var gMustInit = true;
  45. var gAboutToUncollapse = false;
  46.  
  47. function setBlank()
  48. {
  49.     gTimeoutID = null;
  50.     gCurFrame.setAttribute('src', 'chrome://communicator/content/sidebar/PageNotFound.xul');
  51. }
  52.  
  53.  
  54. // Uncomment for debug output
  55. const SB_DEBUG = false;
  56.  
  57. // pref for limiting number of tabs in view
  58. // initialized in sidebar_overlay_init()
  59. var gNumTabsInViewPref;
  60.  
  61. // The rdf service
  62. const RDF_URI = '@mozilla.org/rdf/rdf-service;1';
  63. var RDF = Components.classes[RDF_URI].getService();
  64. RDF = RDF.QueryInterface(Components.interfaces.nsIRDFService);
  65.  
  66. const NC = "http://home.netscape.com/NC-rdf#";
  67.  
  68. // The directory services property to find panels.rdf
  69. const PANELS_RDF_FILE = "UPnls";
  70. const SIDEBAR_VERSION = "0.1";
  71.  
  72. // The default sidebar:
  73. var sidebarObj = new Object;
  74. sidebarObj.never_built = true;
  75.  
  76. //////////////////////////////////////////////////////////////////////
  77. // sbPanelList Class
  78. //
  79. // Wrapper around DOM representation of the sidebar. This UI event
  80. // handlers and the sidebar datasource observers call into this.  This
  81. // class is responsible for keeping the DOM state of the sidebar up to
  82. // date.
  83. // This class does not make any changes to the sidebar rdf datasource.
  84. //////////////////////////////////////////////////////////////////////
  85.  
  86. function sbPanelList(container_id)
  87. {
  88.   debug("sbPanelList("+container_id+")");
  89.   this.node = document.getElementById(container_id);
  90.   this.childNodes = this.node.childNodes;
  91.   this.initialized = false; // set after first display of tabs
  92. }
  93.  
  94. sbPanelList.prototype.get_panel_from_id =
  95. function (id)
  96. {
  97.   debug("get_panel_from_id(" + id + ")");
  98.   var index = 0;
  99.   var header = null;
  100.   if (id && id != '') {
  101.     for (var ii=2; ii < this.node.childNodes.length; ii += 2) {
  102.       header = this.node.childNodes.item(ii);
  103.       if (header.getAttribute('id') == id) {
  104.         debug("get_panel_from_id: Found at index, " + ii);
  105.         index = ii;
  106.         break;
  107.       }
  108.     }
  109.   }
  110.   if (index > 0) {
  111.     return new sbPanel(id, header, index);
  112.   } else {
  113.     return null;
  114.   }
  115. }
  116.  
  117. sbPanelList.prototype.get_panel_from_header_node =
  118. function (node)
  119. {
  120.   return this.get_panel_from_id(node.getAttribute('id'));
  121. }
  122.  
  123. sbPanelList.prototype.get_panel_from_header_index =
  124. function (index)
  125. {
  126.   return this.get_panel_from_header_node(this.node.childNodes.item(index));
  127. }
  128.  
  129. sbPanelList.prototype.find_first =
  130. function (panels)
  131. {
  132.   debug("pick_default_panel: length=" + this.node.childNodes.length);
  133.   for (var ii = 2; ii < this.node.childNodes.length; ii += 2) {
  134.     var panel = this.get_panel_from_header_index(ii);
  135.     if (!panel.is_excluded() && panel.is_in_view()) {
  136.       return panel;
  137.     }
  138.   }
  139.   return null;
  140. }
  141.  
  142. sbPanelList.prototype.find_last =
  143. function (panels)
  144. {
  145.   debug("pick_default_panel: length=" + this.node.childNodes.length);
  146.   for (var ii=(this.node.childNodes.length - 1); ii >= 2; ii -= 2) {
  147.     var panel = this.get_panel_from_header_index(ii);
  148.     if (!panel.is_excluded() && panel.is_in_view()) {
  149.       return panel;
  150.     }
  151.   }
  152.   return null;
  153. }
  154.  
  155. sbPanelList.prototype.visible_panels_exist =
  156. function ()
  157. {
  158.   var i;
  159.   var panels = this.node.childNodes;
  160.   for (i = 2; i < panels.length; i += 2)
  161.   {
  162.     if (!panels.item(i).hidden)
  163.       return true;
  164.   }
  165.   return false;
  166. }
  167.  
  168. sbPanelList.prototype.num_panels_included =
  169. function ()
  170. {
  171.   var count = 0;
  172.   var panels = this.node.childNodes;
  173.   for (var i = 2; i < panels.length; i += 2)
  174.   {
  175.     var curr = this.get_panel_from_header_index(i);
  176.     if (!curr.is_excluded())
  177.       count++;
  178.   }
  179.   return count;
  180. }
  181.  
  182. sbPanelList.prototype.num_panels_in_view =
  183. function ()
  184. {
  185.   var count = 0;
  186.   var panels = this.node.childNodes;
  187.   for (var i = 2; i < panels.length; i += 2)
  188.   {
  189.     var curr = this.get_panel_from_header_index(i);
  190.     if (curr.is_in_view())
  191.       count++;
  192.   }
  193.   return count;
  194. }
  195.  
  196. sbPanelList.prototype.select =
  197. function (panel, force_reload)
  198. {
  199.   if (!force_reload && panel.is_selected()) {
  200.     return;
  201.   }
  202.   // select(): Open this panel and possibly reload it.
  203.   if (this.node.getAttribute('last-selected-panel') != panel.id) {
  204.     // "last-selected-panel" is used as a global variable.
  205.     // this.update() will reference "last-selected-panel".
  206.     // This way the value can be persisted in localstore.rdf.
  207.     this.node.setAttribute('last-selected-panel', panel.id);
  208.   }
  209.   this.update(force_reload);
  210. }
  211.  
  212. sbPanelList.prototype.exclude =
  213. function (panel)
  214. {
  215.   if (this.node.getAttribute('last-selected-panel') == panel.id) {
  216.     this.select_default_panel();
  217.   } else {
  218.     this.update(false);
  219.   }
  220. }
  221.  
  222.  
  223. sbPanelList.prototype.select_default_panel =
  224. function ()
  225. {
  226.   var default_panel = null
  227.  
  228.   // First, check the XUL for the "defaultpanel" attribute of "sidebar-box".
  229.   var sidebar_container = document.getElementById('sidebar-box');
  230.   var content_default_id = sidebar_container.getAttribute('defaultpanel');
  231.   if (content_default_id != '') {
  232.     var content = sidebarObj.panels.get_panel_from_id(content_default_id);
  233.     if (content && !content.is_excluded() && content.is_in_view()) {
  234.       default_panel = content;
  235.     }
  236.   }
  237.  
  238.   // Second, try to use the panel persisted in 'last-selected-panel'.
  239.   if (!default_panel) {
  240.     var last_selected_id = this.node.getAttribute('last-selected-panel');
  241.     if (last_selected_id != '') {
  242.       var last = sidebarObj.panels.get_panel_from_id(last_selected_id);
  243.       if (last && !last.is_excluded() && last.is_in_view()) {
  244.         default_panel = last;
  245.       }
  246.     }
  247.   }
  248.  
  249.   // Finally, just use the last one in the list.
  250.   if (!default_panel) {
  251.     default_panel = this.find_last();
  252.   }
  253.  
  254.   if (default_panel) {
  255.     this.node.setAttribute('last-selected-panel', default_panel.id);
  256.   }
  257.   this.update(false);
  258. }
  259.  
  260. sbPanelList.prototype.refresh =
  261. function ()
  262. {
  263.   var last_selected_id = this.node.getAttribute('last-selected-panel');
  264.   var last_selected = sidebarObj.panels.get_panel_from_id(last_selected_id);
  265.   if (last_selected && last_selected.is_selected()) {
  266.     // The desired panel is already selected
  267.     this.update(false);
  268.   } else {
  269.     this.select_default_panel();
  270.   }
  271. }
  272.  
  273. // panel_loader(): called from a timer that is set in sbPanelList.update()
  274. // Removes the "Loading..." screen when the panel has finished loading.
  275. function panel_loader() {
  276.   debug("panel_loader()");
  277.  
  278.   if (gTimeoutID != null) {
  279.       clearTimeout(gTimeoutID);
  280.       gTimeoutID = null; 
  281.   }
  282.  
  283.   this.removeEventListener("load", panel_loader, true);
  284.   this.removeAttribute('collapsed');
  285.   this.setAttribute('loadstate','loaded');
  286.   this.parentNode.firstChild.setAttribute('hidden','true');
  287. }
  288. sbPanelList.prototype.update =
  289. function (force_reload)
  290. {
  291.   // This function requires that the attribute 'last-selected-panel'
  292.   // holds the id of a non-excluded panel. If it doesn't, no panel will
  293.   // be selected. The attribute is used instead of a funciton
  294.   // parameter to allow the value to be persisted in localstore.rdf.
  295.   var selected_id = this.node.getAttribute('last-selected-panel');
  296.  
  297.   if (sidebar_is_collapsed()) {
  298.     sidebarObj.collapsed = true;
  299.   } else {
  300.     sidebarObj.collapsed = false;
  301.   }
  302.  
  303.   var num_included = sidebarObj.panels.num_panels_included();
  304.   if (num_included > gNumTabsInViewPref)
  305.     document.getElementById("nav-buttons-box").hidden = false;
  306.   else
  307.     document.getElementById("nav-buttons-box").hidden = true;
  308.  
  309.   var have_set_top = 0;
  310.   var have_set_after_selected = 0;
  311.   var is_after_selected = 0;
  312.   var last_header = 0;
  313.   var num_in_view = 0;
  314.   debug("this.initialized: " + this.initialized);
  315.   for (var ii=2; ii < this.node.childNodes.length; ii += 2) {
  316.     var header = this.node.childNodes.item(ii);
  317.     var content = this.node.childNodes.item(ii+1);
  318.     var id = header.getAttribute('id');
  319.     var panel = new sbPanel(id, header, ii);
  320.     var excluded = panel.is_excluded();
  321.     var in_view = false;
  322.     if (!this.initialized) 
  323.     {
  324.       if (num_in_view < gNumTabsInViewPref)
  325.         in_view = true;
  326.     }
  327.     else
  328.     {
  329.       if (header.getAttribute("in-view") == "true")
  330.         in_view = true;
  331.     }
  332.     if (excluded || !in_view)
  333.     {
  334.       debug("item("+ii/2+") excluded: " + excluded + 
  335.                           " in view: " + in_view);
  336.       header.setAttribute('hidden','true');
  337.       content.setAttribute('hidden','true');
  338.       if (!in_view)
  339.       {
  340.         header.setAttribute("in-view", false);
  341.         header.removeAttribute("top-panel");
  342.         header.removeAttribute("last-panel");
  343.       }
  344.     } else {
  345.       // only set if in view
  346.       if (!this.initialized || (num_in_view < gNumTabsInViewPref))
  347.         last_header = header;
  348.       header.removeAttribute('last-panel');
  349.       // only set if in view
  350.       if (!have_set_top && 
  351.           (!this.initialized || (header.getAttribute("in-view") == "true")))
  352.       {
  353.         header.setAttribute('top-panel','true');
  354.         have_set_top = 1;
  355.       } else {
  356.         header.removeAttribute('top-panel');
  357.       }
  358.       if (!have_set_after_selected && is_after_selected) {
  359.         header.setAttribute('first-panel-after-selected','true');
  360.         have_set_after_selected = 1
  361.       } else {
  362.         header.removeAttribute('first-panel-after-selected');
  363.       }
  364.       header.removeAttribute('hidden');
  365.       header.setAttribute("in-view", true);
  366.       num_in_view++;
  367.       
  368.       // (a) when we have hit the maximum number of tabs that can be in view and no tab 
  369.       //     has been selected yet
  370.       //     -or-
  371.       // (b) when we have reached the last tab we are about to display
  372.       if ( ((num_in_view == num_included) ||
  373.             (num_in_view == gNumTabsInViewPref)) &&
  374.           !is_after_selected )
  375.       {
  376.         selected_id = id;
  377.         this.node.setAttribute('last-selected-panel', id);
  378.       }
  379.  
  380.       // Pick sandboxed, or unsandboxed iframe
  381.       var iframe = panel.get_iframe();
  382.       var load_state;
  383.  
  384.       if (selected_id == id) {
  385.         is_after_selected = 1
  386.         debug("item("+ii/2+") selected");
  387.         header.setAttribute('selected', 'true');
  388.         content.removeAttribute('hidden');
  389.         content.removeAttribute('collapsed');
  390.  
  391.         if (sidebarObj.collapsed && panel.is_sandboxed()) {
  392.           if (!panel.is_persistent()) {
  393.             debug("    set src=about:blank");
  394.             iframe.setAttribute('src', 'about:blank');
  395.           }
  396.         } else {
  397.           var saved_src = iframe.getAttribute('content');
  398.           var src = iframe.getAttribute('src');
  399.           // either we have been requested to force_reload or the
  400.           // panel src has changed so we must restore the original src
  401.           if (force_reload || (saved_src != src)) {
  402.             debug("    set src="+saved_src);
  403.             iframe.setAttribute('src', saved_src);
  404.  
  405.             if (gTimeoutID != null)
  406.               clearTimeout(gTimeoutID);
  407.  
  408.             gCurFrame = iframe;
  409.             gTimeoutID = setTimeout (setBlank, 20000);
  410.           }
  411.         }
  412.  
  413.         load_state = content.getAttribute('loadstate');
  414.         if (load_state == 'stopped') {
  415.           load_state = 'never loaded';
  416.           toggleLoadarea(content);
  417.         }
  418.         if (load_state == 'never loaded') {
  419.           iframe.removeAttribute('hidden');
  420.           iframe.setAttribute('loadstate', 'loading');
  421.           iframe.addEventListener('load', panel_loader, true);
  422.         }
  423.       } else {
  424.         debug("item("+ii/2+")");
  425.         header.removeAttribute('selected');
  426.         content.setAttribute('collapsed','true');
  427.  
  428.         if (!panel.is_persistent()) {
  429.           iframe.setAttribute('src', 'about:blank');
  430.           load_state = content.getAttribute('loadstate');
  431.           if (load_state == 'loading') {
  432.             iframe.removeEventListener("load", panel_loader, true);
  433.             content.setAttribute('hidden','true');
  434.             iframe.setAttribute('loadstate', 'never loaded');
  435.           }
  436.         }
  437.         if (panel.is_sandboxed()) {
  438.           if (!panel.is_persistent())
  439.             iframe.setAttribute('src', 'about:blank');
  440.         }
  441.       }
  442.     }
  443.   }
  444.   if (last_header) {
  445.     last_header.setAttribute('last-panel','true');
  446.   }
  447.  
  448.   var no_panels_iframe = document.getElementById('sidebar-iframe-no-panels');
  449.   if (have_set_top) {
  450.       no_panels_iframe.setAttribute('hidden','true');
  451.       // The hide and show of 'sidebar-panels' should not be needed,
  452.       // but some old profiles may have this persisted as hidden (50973).
  453.       this.node.removeAttribute('hidden');
  454.   } else {
  455.       no_panels_iframe.removeAttribute('hidden');
  456.       this.node.setAttribute('hidden','true');
  457.   }
  458.  
  459.   this.initialized = true;
  460. }
  461.  
  462.  
  463. //////////////////////////////////////////////////////////////////////
  464. // sbPanel Class
  465. //
  466. // Like sbPanelList, this class is a wrapper around DOM representation
  467. // of individual panels in the sidebar. This UI event handlers and the
  468. // sidebar datasource observers call into this.  This class is
  469. // responsible for keeping the DOM state of the sidebar up to date.
  470. // This class does not make any changes to the sidebar rdf datasource.
  471. //////////////////////////////////////////////////////////////////////
  472.  
  473. function sbPanel(id, header, index)
  474. {
  475.   // This constructor should only be called by sbPanelList class.
  476.   // To create a panel instance, use the helper functions in sbPanelList:
  477.   //   sb_panel.get_panel_from_id(id)
  478.   //   sb_panel.get_panel_from_header_node(dom_node)
  479.   //   sb_panel.get_panel_from_header_index(index)
  480.   this.id = id;
  481.   this.header = header;
  482.   this.index = index;
  483.   this.parent = sidebarObj.panels;
  484. }
  485.  
  486. sbPanel.prototype.get_header =
  487. function ()
  488. {
  489.   return this.header;
  490. }
  491.  
  492. sbPanel.prototype.get_content =
  493. function ()
  494. {
  495.   return this.get_header().nextSibling;
  496. }
  497.  
  498. sbPanel.prototype.is_sandboxed =
  499. function ()
  500. {
  501.   if (typeof this.sandboxed == "undefined") {
  502.     var content = this.get_content();
  503.     var unsandboxed_iframe = content.childNodes.item(1);
  504.     this.sandboxed = !unsandboxed_iframe.getAttribute('content').match(/^chrome:/);
  505.   }
  506.   return this.sandboxed;
  507. }
  508.  
  509. sbPanel.prototype.get_iframe =
  510. function ()
  511. {
  512.   if (typeof this.iframe == "undefined") {
  513.     var content = this.get_content();
  514.     if (this.is_sandboxed()) {
  515.       var unsandboxed_iframe = content.childNodes.item(2);
  516.       this.iframe = unsandboxed_iframe;
  517.     } else {
  518.       var sandboxed_iframe = content.childNodes.item(1);
  519.       this.iframe = sandboxed_iframe;
  520.     }
  521.   }
  522.   return this.iframe;
  523. }
  524.  
  525. // This exclude function is used on panels and on the panel picker menu.
  526. // That is why it is hanging out in the global name space instead of
  527. // minding its own business in the class.
  528. function sb_panel_is_excluded(node)
  529. {
  530.   var exclude = node.getAttribute('exclude');
  531.   return ( exclude && exclude != '' &&
  532.            exclude.indexOf(sidebarObj.component) != -1 );
  533. }
  534. sbPanel.prototype.is_excluded =
  535. function ()
  536. {
  537.   return sb_panel_is_excluded(this.get_header());
  538. }
  539.  
  540. sbPanel.prototype.is_in_view =
  541. function()
  542. {
  543.   return (this.header.getAttribute("in-view") == "true");
  544. }
  545.  
  546. sbPanel.prototype.is_selected =
  547. function (panel_id)
  548. {
  549.   return 'true' == this.get_header().getAttribute('selected');
  550. }
  551.  
  552. sbPanel.prototype.is_persistent =
  553. function ()
  554. {
  555.     var rv = false;
  556.     var datasource = RDF.GetDataSource(sidebarObj.datasource_uri);
  557.     var persistNode = datasource.GetTarget(RDF.GetResource(this.id),
  558.                                            RDF.GetResource(NC + "persist"),
  559.                                            true);
  560.     if (persistNode)
  561.     {
  562.         persistNode = 
  563.           persistNode.QueryInterface(Components.interfaces.nsIRDFLiteral);
  564.         rv = persistNode.Value == 'true';
  565.     }
  566.  
  567.     return rv;
  568. }
  569.  
  570. sbPanel.prototype.select =
  571. function (force_reload)
  572. {
  573.   this.parent.select(this, force_reload);
  574. }
  575.  
  576. sbPanel.prototype.stop_load =
  577. function ()
  578. {
  579.   var iframe = this.get_iframe();
  580.   var content = this.get_content();
  581.   var load_state = iframe.getAttribute('loadstate');
  582.   if (load_state == "loading") {
  583.     debug("Stop the presses");
  584.     iframe.removeEventListener("load", panel_loader, true);
  585.     content.setAttribute("loadstate", "stopped");
  586.     iframe.setAttribute('src', 'about:blank');
  587.     toggleLoadarea(content);
  588.   }
  589. }
  590.  
  591. function toggleLoadarea(content)
  592. {
  593.   // toggle between "loading" and "load stopped" in the UI
  594.   var widgetBox = content.firstChild.firstChild;
  595.   var widgetBoxKids = widgetBox.childNodes;
  596.   var stopButton = widgetBoxKids.item(3);
  597.   var reloadButton = widgetBoxKids.item(4);
  598.   var loadingImage = widgetBox.firstChild;
  599.   var loadingText = loadingImage.nextSibling;
  600.   var loadStoppedText = loadingText.nextSibling;
  601.  
  602.   // sanity check
  603.   if (stopButton.getAttribute("type") != "stop")
  604.   {
  605.     debug("Error: Expected button of type=\"stop\" but didn't get one!");
  606.     return;
  607.   }
  608.  
  609.   if (!stopButton.hidden)
  610.   {
  611.     // change button from "stop" to "reload"
  612.     stopButton.hidden = "true";
  613.     reloadButton.removeAttribute("hidden");
  614.  
  615.     // hide the loading image and set text to "load stopped"
  616.     loadingImage.hidden = "true";
  617.     loadingText.hidden = "true";
  618.     loadStoppedText.removeAttribute("hidden");
  619.   }
  620.   else
  621.   {
  622.     // change button from "reload" to "stop"
  623.     stopButton.removeAttribute("hidden");
  624.     reloadButton.hidden = "true";
  625.  
  626.     // show the loading image and set text to "loading"
  627.     loadingImage.removeAttribute("hidden");
  628.     loadingText.removeAttribute("hidden");
  629.     loadStoppedText.hidden = "true";
  630.   }
  631. }
  632.  
  633. sbPanel.prototype.exclude =
  634. function ()
  635. {
  636.   // Exclusion is handled by the datasource,
  637.   // but we need to make sure this panel is no longer selected.
  638.   this.get_header().removeAttribute('selected');
  639.   this.parent.exclude(this);
  640. }
  641.  
  642. sbPanel.prototype.reload =
  643. function ()
  644. {
  645.   if (!this.is_excluded()) {
  646.     this.select(true);
  647.   }
  648. }
  649.  
  650. //////////////////////////////////////////////////////////////////
  651. // Panels' RDF Datasource Observer
  652. //
  653. // This observer will ensure that the Sidebar UI stays current
  654. // when the datasource changes.
  655. // - When "refresh" is asserted, the sidebar refreshed.
  656. //   Currently this happens when a panel is included/excluded or
  657. //   added/removed (the later comes from the customize dialog).
  658. // - When "refresh_panel" is asserted, the targeted panel is reloaded.
  659. //   Currently this happens when the customize panel dialog is closed.
  660. //////////////////////////////////////////////////////////////////
  661. var panel_observer = {
  662.   onAssert : function(ds,src,prop,target) {
  663.     //debug ("observer: assert");
  664.     // "refresh" is asserted by select menu and by customize.js.
  665.     if (prop == RDF.GetResource(NC + "refresh")) {
  666.       sidebarObj.panels.initialized = false; // reset so panels are put in view
  667.       sidebarObj.panels.refresh();
  668.     } else if (prop == RDF.GetResource(NC + "refresh_panel")) {
  669.       var panel_id = target.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
  670.       var panel = sidebarObj.panels.get_panel_from_id(panel_id);
  671.       panel.reload();
  672.     }
  673.   },
  674.   onUnassert : function(ds,src,prop,target) {
  675.     //debug ("observer: unassert");
  676.   },
  677.   onChange : function(ds,src,prop,old_target,new_target) {
  678.     //debug ("observer: change");
  679.   },
  680.   onMove : function(ds,old_src,new_src,prop,target) {
  681.     //debug ("observer: move");
  682.   },
  683.   beginUpdateBatch : function(ds) {
  684.     //debug ("observer: beginUpdateBatch");
  685.   },
  686.   endUpdateBatch : function(ds) {
  687.     //debug ("observer: endUpdateBatch");
  688.   }
  689. };
  690.  
  691. // Use an assertion to pass a "refresh" event to all the sidebars.
  692. // They use observers to watch for this assertion (see above).
  693. function refresh_all_sidebars() {
  694.   sidebarObj.datasource.Assert(RDF.GetResource(sidebarObj.resource),
  695.                                RDF.GetResource(NC + "refresh"),
  696.                                RDF.GetLiteral("true"),
  697.                                true);
  698.   sidebarObj.datasource.Unassert(RDF.GetResource(sidebarObj.resource),
  699.                                  RDF.GetResource(NC + "refresh"),
  700.                                  RDF.GetLiteral("true"));
  701. }
  702.  
  703. //////////////////////////////////////////////////////////////
  704. // Sidebar Init
  705. //////////////////////////////////////////////////////////////
  706. function sidebar_overlay_init() {
  707.   if (sidebar_is_collapsed() && !gAboutToUncollapse)
  708.     return;
  709.   gMustInit = false;
  710.   sidebarObj.panels = new sbPanelList('sidebar-panels');
  711.   sidebarObj.datasource_uri = get_sidebar_datasource_uri();
  712.   sidebarObj.resource = 'urn:sidebar:current-panel-list';
  713.  
  714.   sidebarObj.master_datasources = "";
  715.   sidebarObj.master_datasources = get_remote_datasource_url();
  716.   sidebarObj.master_datasources += " chrome://communicator/content/sidebar/local-panels.rdf";
  717.   sidebarObj.master_resource = 'urn:sidebar:master-panel-list';
  718.   sidebarObj.component = document.firstChild.getAttribute('windowtype');
  719.   debug("sidebarObj.component is " + sidebarObj.component);
  720.  
  721.   // Initialize the display
  722.   var sidebar_element = document.getElementById('sidebar-box');
  723.   var sidebar_menuitem = document.getElementById('sidebar-menu');
  724.   if (sidebar_is_hidden()) {
  725.     if (sidebar_menuitem) {
  726.       sidebar_menuitem.setAttribute('checked', 'false');
  727.     }
  728.   } else {
  729.     if (sidebar_menuitem) {
  730.       sidebar_menuitem.setAttribute('checked', 'true');
  731.     }
  732.  
  733.     if (sidebarObj.never_built) {
  734.       sidebarObj.never_built = false;
  735.  
  736.       debug("sidebar = " + sidebarObj);
  737.       debug("sidebarObj.resource = " + sidebarObj.resource);
  738.       debug("sidebarObj.datasource_uri = " + sidebarObj.datasource_uri);
  739.  
  740.       // Obtain the pref for limiting the number of tabs in view
  741.       try
  742.       {
  743.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  744.                       getService(Components.interfaces.nsIPrefBranch);
  745.         gNumTabsInViewPref = prefs.getIntPref("sidebar.num_tabs_in_view");
  746.       }
  747.       catch (ex)
  748.       {
  749.         gNumTabsInViewPref = 8; // failover default
  750.       }
  751.  
  752.       // Show the header for the panels area. Use a splitter if there
  753.       // is stuff over the panels area.
  754.       var sidebar_panels_splitter = document.getElementById('sidebar-panels-splitter');
  755.       if (sidebar_element.firstChild != sidebar_panels_splitter) {
  756.         debug("Showing the panels splitter");
  757.         sidebar_panels_splitter.removeAttribute('hidden');
  758.       }
  759.  
  760.       // Add the user's current panel choices to the template builder,
  761.       // which will aggregate it with the other datasources that describe
  762.       // the individual panel's title, customize URL, and content URL.
  763.       var panels = document.getElementById('sidebar-panels');
  764.       panels.database.AddDataSource(RDF.GetDataSource(sidebarObj.datasource_uri));
  765.  
  766.       debug("Adding observer to database.");
  767.       panels.database.AddObserver(panel_observer);
  768.  
  769.       // XXX This is a hack to force re-display
  770.       panels.setAttribute('ref', sidebarObj.resource);
  771.     }
  772.     if (sidebar_is_collapsed()) {
  773.       sidebarObj.collapsed = true;
  774.     } else {
  775.       sidebarObj.collapsed = false;
  776.     }
  777.  
  778.     sidebar_open_default_panel(100, 0);
  779.   }
  780. }
  781.  
  782. function sidebar_overlay_destruct() {
  783.     var panels = document.getElementById('sidebar-panels');
  784.     debug("Removing observer from database.");
  785.     panels.database.RemoveObserver(panel_observer);
  786. }
  787.  
  788. function sidebar_open_default_panel(wait, tries) {
  789.   var panels = document.getElementById('sidebar-panels');
  790.  
  791.   debug("sidebar_open_default_panel("+wait+","+tries+")");
  792.  
  793.   // Make sure the sidebar exists before trying to refresh it.
  794.   if (panels.childNodes.length <= 2) {
  795.     if (tries < 3) {
  796.       // No children yet, try again later
  797.       setTimeout('sidebar_open_default_panel('+(wait*2)+','+(tries+1)+')',wait);
  798.       return;
  799.     } else {
  800.       sidebar_fixup_datasource();
  801.     }
  802.   }
  803.   sidebarObj.panels.refresh();
  804. }
  805.  
  806. //////////////////////////////////////////////////////////////
  807. // Sidebar File and Datasource functions
  808. //////////////////////////////////////////////////////////////
  809.  
  810. function sidebar_get_panels_file() {
  811.   try {
  812.     var locator_service = Components.classes["@mozilla.org/file/directory_service;1"].getService();
  813.     if (locator_service)
  814.       locator_service = locator_service.QueryInterface(Components.interfaces.nsIProperties);
  815.     // Use the fileLocator to look in the profile directory to find
  816.     // 'panels.rdf', which is the database of the user's currently
  817.     // selected panels.
  818.     // If <profile>/panels.rdf doesn't exist, GetFileLocation() will copy
  819.     // bin/defaults/profile/panels.rdf to <profile>/panels.rdf
  820.     var sidebar_file = locator_service.get(PANELS_RDF_FILE, Components.interfaces.nsIFile);
  821.     if (!sidebar_file.exists()) {
  822.       // This should not happen, as GetFileLocation() should copy
  823.       // defaults/panels.rdf to the users profile directory
  824.       debug("Sidebar panels file does not exist");
  825.       throw("Panels file does not exist");
  826.     }
  827.     return sidebar_file;
  828.   } catch (ex) {
  829.     // This should not happen
  830.     debug("Error: Unable to grab panels file.\n");
  831.     throw(ex);
  832.   }
  833.   return null;
  834. }
  835.  
  836. function sidebar_revert_to_default_panels() {
  837.   try {
  838.     var sidebar_file = sidebar_get_panels_file();
  839.  
  840.     // Calling delete() with array notation (workaround for bug 37406).
  841.     sidebar_file["delete"](false);
  842.  
  843.     // Since we just removed the panels file,
  844.     // this should copy the defaults over.
  845.     sidebar_file = sidebar_get_panels_file();
  846.  
  847.     debug("sidebar defaults reloaded");
  848.     var datasource = RDF.GetDataSource(sidebarObj.datasource_uri);
  849.     datasource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Refresh(true);
  850.   } catch (ex) {
  851.     debug("Error: Unable to reload panel defaults file.\n");
  852.   }
  853.   return null;
  854. }
  855.  
  856. function get_sidebar_datasource_uri() {
  857.   try {
  858.     var sidebar_file = sidebar_get_panels_file();
  859.     
  860.     var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
  861.     
  862.     return ioService.getURLSpecFromFile(sidebar_file);
  863.   } catch (ex) {
  864.     // This should not happen
  865.     debug("Error: Unable to load panels file.\n");
  866.   }
  867.   return null;
  868. }
  869.  
  870. // Get the template for the available panels url from preferences.
  871. // Replace variables in the url:
  872. //     %LOCALE%  -->  Application locale (e.g. en-us).
  873. //     %VERSION% --> Sidebar file format version (e.g. 0.0).
  874. function get_remote_datasource_url() {
  875.   var url = '';
  876.   var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  877.                         .getService(Components.interfaces.nsIPrefBranch);
  878.   var locale;
  879.   try {
  880.     url = prefs.getCharPref("sidebar.customize.all_panels.url");
  881.     url = url.replace(/%SIDEBAR_VERSION%/g, SIDEBAR_VERSION);
  882.   } catch(ex) {
  883.     debug("Unable to get remote url pref. What now? "+ex);
  884.   }
  885.   try {
  886.     locale = prefs.getComplexValue("intl.content.langcode",
  887.                                    Components.interfaces.nsIPrefLocalizedString);
  888.   } catch(ex) {
  889.     try {
  890.       debug("No lang code pref, intl.content.langcode.");
  891.       debug("Use locale from user agent string instead");
  892.  
  893.       locale = prefs.getComplexValue("general.useragent.locale",
  894.                                      Components.interfaces.nsIPrefLocalizedString);
  895.   } catch(ex) {
  896.       debug("Unable to get system locale. What now? "+ex);
  897.     }
  898.   }
  899.   locale = locale.data.toLowerCase();
  900.   url = url.replace(/%LOCALE%/g, locale);
  901.  
  902.   debug("Remote url is " + url);
  903.   return url;
  904. }
  905.  
  906. function sidebar_fixup_datasource() {
  907.   var datasource = RDF.GetDataSource(sidebarObj.datasource_uri);
  908.   var resource = RDF.GetResource(sidebarObj.resource);
  909.  
  910.   var panel_list = datasource.GetTarget(resource,
  911.                                         RDF.GetResource(NC+"panel-list"),
  912.                                         true);
  913.   if (!panel_list) {
  914.     debug("Sidebar datasource is an old format or busted\n");
  915.     sidebar_revert_to_default_panels();
  916.   } else {
  917.     // The datasource is ok, but it just has no panels.
  918.     // sidebar_refresh() will display some helper content.
  919.     // Do nothing here.
  920.   }
  921. }
  922.  
  923. //////////////////////////////////////////////////////////////
  924. // Sidebar Interface for XUL
  925. //////////////////////////////////////////////////////////////
  926.  
  927. // Change the sidebar content to the selected panel.
  928. // Called when a panel title is clicked.
  929. function SidebarSelectPanel(header, should_popopen, should_unhide) {
  930.   debug("SidebarSelectPanel("+header+","+should_popopen+","+should_unhide+")");
  931.   var panel = sidebarObj.panels.get_panel_from_header_node(header);
  932.  
  933.   if (!panel) {
  934.     return false;
  935.   }
  936.  
  937.   var popopen = false;
  938.   var unhide = false;
  939.  
  940.   if (panel.is_excluded()) {
  941.     return false;
  942.   }
  943.   if (sidebar_is_hidden()) {
  944.     if (should_unhide) {
  945.       unhide = true;
  946.     } else {
  947.       return false;
  948.     }
  949.   }
  950.   if (sidebar_is_collapsed()) {
  951.     if (should_popopen) {
  952.       popopen = true;
  953.     } else {
  954.       return false;
  955.     }
  956.   }
  957.   if (unhide)  SidebarShowHide();
  958.   if (popopen) SidebarExpandCollapse();
  959.   if (!panel.is_selected()) panel.select(false);
  960.  
  961.   return true;
  962. }
  963.  
  964. function SidebarGetLastSelectedPanel()
  965. {
  966.   return (sidebarObj.panels && 
  967.           sidebarObj.panels.node.getAttribute('last-selected-panel'));
  968. }
  969.  
  970. function SidebarGetRelativePanel(direction)
  971. {
  972.   // direction == 1 to view next panel, -1 to view prev panel
  973.  
  974.   if (sidebar_is_hidden())
  975.     SidebarShowHide();
  976.   if (sidebar_is_collapsed())
  977.     SidebarExpandCollapse();
  978.  
  979.   var currentPanel = sidebarObj.panels.get_panel_from_id(SidebarGetLastSelectedPanel());
  980.   if (!currentPanel) {
  981.     sidebarObj.panels.select_default_panel();
  982.     return;
  983.   }
  984.  
  985.   var newPanel = currentPanel;
  986.  
  987.   do {
  988.     var newPanelIndex = newPanel.index + (direction * 2);
  989.     if (newPanelIndex < 2 || newPanelIndex >= sidebarObj.panels.node.childNodes.length)
  990.       newPanel = (direction == 1)? sidebarObj.panels.find_first(): sidebarObj.panels.find_last();
  991.     else 
  992.       newPanel = sidebarObj.panels.get_panel_from_header_index(newPanelIndex);
  993.  
  994.     if (!newPanel)
  995.       break;
  996.  
  997.     if (!newPanel.is_excluded()) {
  998.       SidebarSelectPanel(newPanel.header, true, true);  // found a panel that's not excluded to select -- do it
  999.       break;
  1000.     }
  1001.   } while (newPanel != currentPanel);  // keep looking for a panel, but don't loop infinitely
  1002. }
  1003.  
  1004. function SidebarStopPanelLoad(header) {
  1005.   var panel = sidebarObj.panels.get_panel_from_header_node(header);
  1006.   panel.stop_load();
  1007. }
  1008.  
  1009. function SidebarReloadPanel(header) {
  1010.   var panel = sidebarObj.panels.get_panel_from_header_node(header);
  1011.   panel.reload();
  1012. }
  1013.  
  1014. // No one is calling this right now.
  1015. function SidebarReload() {
  1016.   sidebarObj.panels.refresh();
  1017. }
  1018.  
  1019. function SidebarRebuild() {
  1020.   sidebarObj.panels.initialized = false; // reset so panels are brought in view
  1021.   var panels = document.getElementById("sidebar-panels");
  1022.   panels.builder.rebuild();
  1023.   sidebar_open_default_panel(100, 0);
  1024. }
  1025.  
  1026. // Set up a lame hack to avoid opening two customize
  1027. // windows on a double click.
  1028. var gDisableCustomize = false;
  1029. function enable_customize() {
  1030.   gDisableCustomize = false;
  1031. }
  1032.  
  1033. // Bring up the Sidebar customize dialog.
  1034. function SidebarCustomize() {
  1035.   // Use a single sidebar customize dialog
  1036.   var cwindowManager = Components.classes['@mozilla.org/rdf/datasource;1?name=window-mediator'].getService();
  1037.   var iwindowManager = Components.interfaces.nsIWindowMediator;
  1038.   var windowManager  = cwindowManager.QueryInterface(iwindowManager);
  1039.  
  1040.   var customizeWindow = windowManager.getMostRecentWindow('sidebar:customize');
  1041.  
  1042.   if (customizeWindow) {
  1043.     debug("Reuse existing customize dialog");
  1044.     customizeWindow.focus();
  1045.   } else {
  1046.     debug("Open a new customize dialog");
  1047.  
  1048.     if (false == gDisableCustomize) {
  1049.       debug("First time creating customize dialog");
  1050.       gDisableCustomize = true;
  1051.  
  1052.       var panels = document.getElementById('sidebar-panels');
  1053.  
  1054.       customizeWindow = window.openDialog(
  1055.                          'chrome://communicator/content/sidebar/customize.xul',
  1056.                          '_blank','centerscreen,chrome,resizable,dialog=no,dependent',
  1057.                          sidebarObj.master_datasources,
  1058.                          sidebarObj.master_resource,
  1059.                          sidebarObj.datasource_uri,
  1060.                          sidebarObj.resource);
  1061.       setTimeout(enable_customize, 2000);
  1062.     }
  1063.   }
  1064. }
  1065.  
  1066.  
  1067.  
  1068. function BrowseMorePanels()
  1069. {
  1070.   var url = '';
  1071.   var browser_url = "chrome://navigator/content/navigator.xul";
  1072.   var locale;
  1073.   try {
  1074.     var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  1075.                           .getService(Components.interfaces.nsIPrefBranch);
  1076.     url = prefs.getCharPref("sidebar.customize.directory.url");
  1077.     var temp = prefs.getCharPref("browser.chromeURL");
  1078.     if (temp) browser_url = temp;
  1079.   } catch(ex) {
  1080.     debug("Unable to get prefs: "+ex);
  1081.   }
  1082.   window.openDialog(browser_url, "_blank", "chrome,all,dialog=no", url);
  1083. }
  1084.  
  1085.  
  1086.  
  1087. function sidebar_is_collapsed() {
  1088.   var sidebar_splitter = document.getElementById('sidebar-splitter');
  1089.   return (sidebar_splitter &&
  1090.           sidebar_splitter.getAttribute('state') == 'collapsed');
  1091. }
  1092.  
  1093. function SidebarExpandCollapse() {
  1094.   var sidebar_splitter = document.getElementById('sidebar-splitter');
  1095.   var sidebar_box = document.getElementById('sidebar-box');
  1096.   if (sidebar_splitter.getAttribute('state') == 'collapsed') {
  1097.     if (gMustInit)
  1098.       sidebar_overlay_init();
  1099.     debug("Expanding the sidebar");
  1100.     sidebar_splitter.removeAttribute('state');
  1101.     sidebar_box.removeAttribute('collapsed');
  1102.   } else {
  1103.     debug("Collapsing the sidebar");
  1104.     sidebar_splitter.setAttribute('state', 'collapsed');
  1105.     sidebar_box.setAttribute('collapsed', 'true');
  1106.   }
  1107. }
  1108.  
  1109. // sidebar_is_hidden() - Helper function for SidebarShowHide().
  1110. function sidebar_is_hidden() {
  1111.   var sidebar_title = document.getElementById('sidebar-title-box');
  1112.   var sidebar_box = document.getElementById('sidebar-box');
  1113.   return sidebar_box.getAttribute('hidden') == 'true'
  1114.          || sidebar_title.getAttribute('hidden') == 'true';
  1115. }
  1116.  
  1117. // Show/Hide the entire sidebar.
  1118. // Invoked by the "View / Sidebar" menu option.
  1119. function SidebarShowHide() {
  1120.   var sidebar_box = document.getElementById('sidebar-box');
  1121.   var title_box = document.getElementById('sidebar-title-box');
  1122.   var sidebar_panels_splitter = document.getElementById('sidebar-panels-splitter');
  1123.   var sidebar_panels_splitter_box = document.getElementById('sidebar-panels-splitter-box');
  1124.   var sidebar_splitter = document.getElementById('sidebar-splitter');
  1125.   var sidebar_menu_item = document.getElementById('sidebar-menu');
  1126.   var tabs_menu = document.getElementById('sidebar-panel-picker');
  1127.  
  1128.   if (sidebar_is_hidden()) {
  1129.     debug("Showing the sidebar");
  1130.  
  1131.     // for older profiles: 
  1132.     sidebar_box.setAttribute('hidden', 'false'); 
  1133.     sidebar_panels_splitter_box.setAttribute('hidden', 'false'); 
  1134.  
  1135.     sidebar_box.removeAttribute('collapsed');
  1136.     if (sidebar_splitter.getAttribute('state') == 'collapsed')
  1137.       sidebar_splitter.removeAttribute('state');
  1138.     title_box.removeAttribute('hidden');
  1139.     sidebar_panels_splitter_box.removeAttribute('collapsed');
  1140.     sidebar_splitter.removeAttribute('hidden');
  1141.     if (sidebar_box.firstChild != sidebar_panels_splitter) {
  1142.       debug("Showing the panels splitter");
  1143.       sidebar_panels_splitter.removeAttribute('hidden');
  1144.     }
  1145.     sidebar_overlay_init();
  1146.     sidebar_menu_item.setAttribute('checked', 'true');
  1147.     tabs_menu.removeAttribute('hidden');
  1148.   } else {
  1149.     debug("Hiding the sidebar");
  1150.     var hide_everything = sidebar_panels_splitter.getAttribute('hidden') == 'true';
  1151.     if (hide_everything) {
  1152.       debug("Hide everything");
  1153.       sidebar_box.setAttribute('collapsed', 'true');
  1154.       sidebar_splitter.setAttribute('hidden', 'true');
  1155.     } else {
  1156.       sidebar_panels_splitter.setAttribute('hidden', 'true');
  1157.     }
  1158.     title_box.setAttribute('hidden', 'true');
  1159.     sidebar_panels_splitter_box.setAttribute('collapsed', 'true');
  1160.     sidebar_menu_item.setAttribute('checked', 'false');
  1161.     tabs_menu.setAttribute('hidden', 'true');
  1162.   }
  1163.   // Immediately save persistent values
  1164.   document.persist('sidebar-title-box', 'hidden');
  1165.   persist_width();
  1166.   window._content.focus();
  1167. }
  1168.  
  1169. function SidebarBuildPickerPopup() {
  1170.   var menu = document.getElementById('sidebar-panel-picker-popup');
  1171.   menu.database.AddDataSource(RDF.GetDataSource(sidebarObj.datasource_uri));
  1172.   menu.setAttribute('ref', sidebarObj.resource);
  1173.  
  1174.   for (var ii=3; ii < menu.childNodes.length; ii++) {
  1175.     var panel_menuitem = menu.childNodes.item(ii);
  1176.     if (sb_panel_is_excluded(panel_menuitem)) {
  1177.       debug(ii+": "+panel_menuitem.getAttribute('label')+ ": excluded; uncheck.");
  1178.       panel_menuitem.removeAttribute('checked');
  1179.     } else {
  1180.       debug(ii+": "+panel_menuitem.getAttribute('label')+ ": included; check.");
  1181.       panel_menuitem.setAttribute('checked', 'true');
  1182.     }
  1183.   }
  1184. }
  1185.  
  1186. function SidebarTogglePanel(panel_menuitem) {
  1187.   // Create a "container" wrapper around the current panels to
  1188.   // manipulate the RDF:Seq more easily.
  1189.   sidebarObj.datasource = RDF.GetDataSource(sidebarObj.datasource_uri);
  1190.  
  1191.   var did_exclude = false;
  1192.   var panel_id = panel_menuitem.getAttribute('id');
  1193.   var panel = sidebarObj.panels.get_panel_from_id(panel_id);
  1194.   var panel_exclude = panel_menuitem.getAttribute('exclude')
  1195.   if (panel_exclude == '') {
  1196.     // Nothing excluded for this panel yet, so add this component to the list.
  1197.     debug("Excluding " + panel_id + " from " + sidebarObj.component);
  1198.     sidebarObj.datasource.Assert(RDF.GetResource(panel_id),
  1199.                                 RDF.GetResource(NC + "exclude"),
  1200.                                 RDF.GetLiteral(sidebarObj.component),
  1201.                                 true);
  1202.     panel.exclude();
  1203.     did_exclude = true;
  1204.   } else {
  1205.     // Panel has an exclude string, but it may or may not have the
  1206.     // current component listed in the string.
  1207.     debug("Current exclude string: " + panel_exclude);
  1208.     var new_exclude = panel_exclude;
  1209.     if (sb_panel_is_excluded(panel_menuitem)) {
  1210.       debug("Plucking this component out of the exclude list");
  1211.       replace_pat = new RegExp(sidebarObj.component + "\s*");
  1212.       new_exclude = new_exclude.replace(replace_pat,'');
  1213.       new_exclude = new_exclude.replace(/^\s+/,'');
  1214.       // did_exclude remains false
  1215.     } else {
  1216.       debug("Adding this component to the exclude list");
  1217.       new_exclude = new_exclude + " " + sidebarObj.component;
  1218.       panel.exclude();
  1219.       did_exclude = true;
  1220.     }
  1221.     if (new_exclude == '') {
  1222.       debug("Removing exclude list");
  1223.       sidebarObj.datasource.Unassert(RDF.GetResource(panel_id),
  1224.                                      RDF.GetResource(NC + "exclude"),
  1225.                                      RDF.GetLiteral(sidebarObj.component));
  1226.     } else {
  1227.       debug("New exclude string: " + new_exclude);
  1228.       exclude_target =
  1229.         sidebarObj.datasource.GetTarget(RDF.GetResource(panel_id),
  1230.                                         RDF.GetResource(NC + "exclude"),
  1231.                                         true);
  1232.       sidebarObj.datasource.Change(RDF.GetResource(panel_id),
  1233.                                    RDF.GetResource(NC + "exclude"),
  1234.                                    exclude_target,
  1235.                                    RDF.GetLiteral(new_exclude));
  1236.     }
  1237.   }
  1238.  
  1239.   if (did_exclude)
  1240.   {
  1241.     // if we excluded a tab in view then add another one
  1242.     var tabs = sidebarObj.panels.node.childNodes;
  1243.     if (panel.is_in_view())
  1244.     {
  1245.       // we excluded one so let's try to bring a non-excluded one into view
  1246.       var newFirst = null;
  1247.       var added = false;
  1248.       for (var i = 2; i < tabs.length ; i += 2)
  1249.       {
  1250.         var currTab = sidebarObj.panels.get_panel_from_header_index(i);
  1251.         var hasPotential = !currTab.is_excluded() && !currTab.is_in_view();
  1252.  
  1253.         // set potential new first tab in case we can't find one after the
  1254.         // tab that was just excluded
  1255.         if (!newFirst && hasPotential)
  1256.           newFirst = currTab;
  1257.  
  1258.         if (i > panel.index && hasPotential)
  1259.         {
  1260.           currTab.header.setAttribute("in-view", true);
  1261.           added = true;
  1262.           break;
  1263.         }
  1264.       }
  1265.       if (!added && newFirst)
  1266.         newFirst.header.setAttribute("in-view", true);
  1267.  
  1268.       // lose it from current view
  1269.       panel.header.setAttribute("in-view", false);
  1270.     }
  1271.   }
  1272.   else
  1273.   {
  1274.     panel.header.setAttribute("in-view", true);
  1275.  
  1276.     // if we have one too many tabs we better get rid of an old one
  1277.     if (sidebarObj.panels.num_panels_in_view() > gNumTabsInViewPref)
  1278.     {
  1279.       // we included a new tab so let's take the last one out of view
  1280.       for (i = 2; i < tabs.length; i += 2)
  1281.       {
  1282.         var currHeader = tabs[i];
  1283.         if (currHeader.hasAttribute("last-panel"))
  1284.           currHeader.setAttribute("in-view", false);
  1285.       }
  1286.     }
  1287.  
  1288.     panel.select(false);
  1289.   }
  1290.  
  1291.   if (did_exclude && !sidebarObj.panels.visible_panels_exist())
  1292.     // surrender focus to main content area
  1293.     window._content.focus();
  1294.   else
  1295.     // force all the sidebars to update
  1296.     refresh_all_sidebars();
  1297.  
  1298.   // Write the modified panels out.
  1299.   sidebarObj.datasource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush();
  1300. }
  1301.  
  1302. function SidebarNavigate(aDirection)
  1303. {
  1304.   debug("SidebarNavigate " + aDirection);
  1305.  
  1306.   var tabs = sidebarObj.panels.node.childNodes;
  1307.   var i;
  1308.   var currHeader;
  1309.   var currTab;
  1310.   // move forward a tab (down in the template)
  1311.   if (aDirection > 0)
  1312.   {
  1313.     // ensure we have a tab below the last one
  1314.     var foundLast = false; 
  1315.     var oldFirst = null;
  1316.     for (i = 2; i < tabs.length; i += 2) 
  1317.     {
  1318.       currHeader = tabs[i];
  1319.       currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i);
  1320.   
  1321.       if (!currTab.is_excluded())
  1322.       {     
  1323.         if (foundLast)
  1324.         {
  1325.           debug("toggling old first and new last");
  1326.           debug("new last:  " + currHeader.getAttribute("id"));
  1327.           debug("old first: " + oldFirst.getAttribute("id"));
  1328.           currHeader.setAttribute("in-view", true);
  1329.           oldFirst.setAttribute("in-view", false);
  1330.           
  1331.           // if old first was selected select new first instead
  1332.           if (oldFirst.getAttribute("id") == 
  1333.               sidebarObj.panels.node.getAttribute("last-selected-panel"))
  1334.           {
  1335.             sidebarObj.panels.node.setAttribute('last-selected-panel', 
  1336.               currTab.id);
  1337.           }
  1338.           
  1339.           break;
  1340.         }
  1341.  
  1342.         if (!foundLast && currHeader.hasAttribute("last-panel"))
  1343.         {
  1344.           debug("found last");
  1345.           foundLast = true;
  1346.         }
  1347.  
  1348.         // set the old first in case we find a new last below
  1349.         // the old last and need to toggle the new first's ``in-view''
  1350.         if (!oldFirst && currTab.is_in_view())
  1351.           oldFirst = currHeader;
  1352.       }
  1353.     }
  1354.   }
  1355.   
  1356.   // move back a tab (up in the template)
  1357.   else if (aDirection < 0)
  1358.   {
  1359.     var newFirst = null, newLast = null;
  1360.     var foundFirst = false;
  1361.     for (i = 2; i < tabs.length; i += 2)
  1362.     {
  1363.       currHeader = tabs[i];
  1364.       currTab = new sbPanel(currHeader.getAttribute("id"), currHeader, i);
  1365.  
  1366.       if (!currTab.is_excluded())
  1367.       {
  1368.         if (!foundFirst && currHeader.hasAttribute("top-panel"))
  1369.         {
  1370.           debug("found first");
  1371.           foundFirst = true;
  1372.         }
  1373.         if (!foundFirst)
  1374.         {
  1375.           debug("setting newFirst");
  1376.           newFirst = currHeader;
  1377.         }
  1378.  
  1379.         if (currHeader.hasAttribute("last-panel"))
  1380.         {
  1381.           debug("found last");
  1382.  
  1383.           // ensure we have a tab above the first one
  1384.           if (newFirst)
  1385.           {
  1386.             debug("toggling new first and old last");
  1387.             debug("new first: " + newFirst.getAttribute("id"));
  1388.             debug("old last:  " + currHeader.getAttribute("id"));
  1389.  
  1390.             newFirst.setAttribute("in-view", true);
  1391.             currHeader.setAttribute("in-view", false); // hide old last
  1392.           
  1393.             // if old last was selected, now select one above it
  1394.             if (sidebarObj.panels.node.getAttribute("last-selected-panel") ==
  1395.                 currTab.id)
  1396.             {
  1397.               sidebarObj.panels.node.setAttribute("last-selected-panel", 
  1398.                 newLast.getAttribute("id"));
  1399.             }
  1400.  
  1401.             break;
  1402.           }
  1403.         }
  1404.         if (currTab.is_in_view())
  1405.           newLast = currHeader;
  1406.       }
  1407.     }
  1408.   }
  1409.  
  1410.   if (aDirection)
  1411.     sidebarObj.panels.update(false);
  1412. }
  1413.  
  1414. //////////////////////////////////////////////////////////////
  1415. // Sidebar Hacks and Work-arounds
  1416. //////////////////////////////////////////////////////////////
  1417.  
  1418. // SidebarCleanUpExpandCollapse() - Respond to grippy click.
  1419. function SidebarCleanUpExpandCollapse() {
  1420.   // XXX Mini hack. Persist isn't working too well. Force the persist,
  1421.   // but wait until the change has commited.
  1422.   if (gMustInit) {
  1423.     gAboutToUncollapse = true;
  1424.     sidebar_overlay_init();
  1425.   }
  1426.  
  1427.   setTimeout("document.persist('sidebar-box', 'collapsed');",100);
  1428.   setTimeout("sidebarObj.panels.refresh();",100);
  1429. }
  1430.  
  1431. function PersistHeight() {
  1432.   // XXX Mini hack. Persist isn't working too well. Force the persist,
  1433.   // but wait until the last drag has been committed.
  1434.   // May want to do something smarter here like only force it if the
  1435.   // height has really changed.
  1436.   setTimeout("document.persist('sidebar-panels-splitter-box','height');",100);
  1437. }
  1438.  
  1439. function persist_width() {
  1440.   // XXX Mini hack. Persist isn't working too well. Force the persist,
  1441.   // but wait until the width change has commited.
  1442.   setTimeout("document.persist('sidebar-box', 'width');",100);
  1443. }
  1444.  
  1445. function SidebarFinishClick() {
  1446.  
  1447.   // XXX Semi-hack for bug #16516.
  1448.   // If we had the proper drag event listener, we would not need this
  1449.   // timeout. The timeout makes sure the width is written to disk after
  1450.   // the sidebar-box gets the newly dragged width.
  1451.   setTimeout("persist_width()",100);
  1452.  
  1453.   var is_collapsed = document.getElementById('sidebar-box').getAttribute('collapsed') == 'true' ? true : false;
  1454.   debug("collapsed: " + is_collapsed);
  1455.   if (is_collapsed != sidebarObj.collapsed) {
  1456.     if (gMustInit)
  1457.       sidebar_overlay_init();
  1458.     sidebarObj.panels.refresh();
  1459.   }
  1460. }
  1461.  
  1462. function SidebarInitContextMenu(aMenu, aPopupNode)
  1463. {
  1464.   var panel = sidebarObj.panels.get_panel_from_header_node(aPopupNode);
  1465.   var switchItem = document.getElementById("switch-ctx-item");
  1466.   var reloadItem = document.getElementById("reload-ctx-item");
  1467.   var stopItem = document.getElementById("stop-ctx-item");
  1468.  
  1469.   // the current panel can be reloaded, but other panels are not showing 
  1470.   // any content, so we only allow you to switch to other panels
  1471.   if (panel.is_selected()) 
  1472.   {
  1473.     switchItem.setAttribute("collapsed", "true"); 
  1474.     reloadItem.removeAttribute("disabled");
  1475.   }
  1476.   else 
  1477.   {
  1478.     switchItem.removeAttribute("collapsed");
  1479.     reloadItem.setAttribute("disabled", "true");
  1480.   }
  1481.  
  1482.   // only if a panel is currently loading enable the ``Stop'' item
  1483.   if (panel.get_iframe().getAttribute("loadstate") == "loading")
  1484.     stopItem.removeAttribute("disabled");
  1485.   else
  1486.     stopItem.setAttribute("disabled", "true");
  1487. }
  1488.  
  1489. ///////////////////////////////////////////////////////////////
  1490. // Handy Debug Tools
  1491. //////////////////////////////////////////////////////////////
  1492. var debug = null;
  1493. var dump_attributes = null;
  1494. var dump_tree = null;
  1495. if (!SB_DEBUG) {
  1496.   debug = function (s) {};
  1497.   dump_attributes = function (node, depth) {};
  1498.   dump_tree = function (node) {};
  1499.   var _dump_tree_recur = function (node, depth, index) {};
  1500. } else {
  1501.   debug = function (s) { dump("-*- sbOverlay: " + s + "\n"); };
  1502.  
  1503.   dump_attributes = function (node, depth) {
  1504.     var attributes = node.attributes;
  1505.     var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | . ";
  1506.  
  1507.     if (!attributes || attributes.length == 0) {
  1508.       debug(indent.substr(indent.length - depth*2) + "no attributes");
  1509.     }
  1510.     for (var ii=0; ii < attributes.length; ii++) {
  1511.       var attr = attributes.item(ii);
  1512.       debug(indent.substr(indent.length - depth*2) + attr.name +
  1513.             "=" + attr.value);
  1514.     }
  1515.   }
  1516.   dump_tree = function (node) {
  1517.     _dump_tree_recur(node, 0, 0);
  1518.   }
  1519.   _dump_tree_recur = function (node, depth, index) {
  1520.     if (!node) {
  1521.       debug("dump_tree: node is null");
  1522.     }
  1523.     var indent = "| | | | | | | | | | | | | | | | | | | | | | | | | | | | + ";
  1524.     debug(indent.substr(indent.length - depth*2) + index +
  1525.           " " + node.nodeName);
  1526.     if (node.nodeName != "#text") {
  1527.       dump_attributes(node, depth);
  1528.     }
  1529.     var kids = node.childNodes;
  1530.     for (var ii=0; ii < kids.length; ii++) {
  1531.       _dump_tree_recur(kids[ii], depth + 1, ii);
  1532.     }
  1533.   }
  1534. }
  1535.  
  1536. //////////////////////////////////////////////////////////////
  1537. // Install the load/unload handlers
  1538. //////////////////////////////////////////////////////////////
  1539. addEventListener("load", sidebar_overlay_init, false);
  1540. addEventListener("unload", sidebar_overlay_destruct, false);
  1541.